package ru.ifmo.mlarinsky.classificator.math;

import java.util.*;

import ru.ifmo.mlarinsky.classificator.map.MapNode;
import ru.ifmo.mlarinsky.classificator.model.Quote;

public class MathUtil {

    private MathUtil() {
    }

    public static double mapDist(MapNode a, MapNode b) {
        return Math.sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
    }

    public static double vectDist(MapNode a, MapNode b) {
        double[] aValues = a.getValues();
        double[] bValues = b.getValues();
        int length = aValues.length;

        double sqrtSum = 0;
        for (int i = 0; i < length; i++) {
            sqrtSum += (aValues[i] - bValues[i]) * (aValues[i] - bValues[i]);
        }

        return Math.sqrt(sqrtSum);
    }

    public static double diam(Set<MapNode> points) {
        double diam = 0;

        for (MapNode mapPointA : points) {
            for (MapNode mapPointB : points) {
                diam = Math.max(diam, mapDist(mapPointA, mapPointB));
            }
        }

        return diam;
    }

    public static double returnRate(Quote prevQuote, Quote currQuote) {
        return 1 - (prevQuote.getClosePrice() / currQuote.getClosePrice());
    }

    public static double getPriceSum(List<Quote> quotes, Quote.QuoteField field) {
        double sum = 0;

        for (Quote quote : quotes) {
            double fieldValue;
            switch (field) {
                case HIGH_PRICE:
                    fieldValue = quote.getHighPrice();
                    break;
                case CLOSE_PRICE:
                    fieldValue = quote.getClosePrice();
                    break;
                case LOW_PRICE:
                    fieldValue = quote.getLowPrice();
                    break;
                case OPEN_PRICE:
                    fieldValue = quote.getLowPrice();
                    break;
                default:
                    throw new IllegalArgumentException("Field must be HIGH_PRICE, CLOSE_PRICE, LOW_PRICE or OPEN_PRICE. "
                            + "But it is " + field.toString());
            }

            sum += fieldValue;
        }

        return sum;
    }

    public static double determination(Double[] instrumentReturnRates, Double[] indexReturnRates) {
        double sumIP = 0;
        double sumI = 0;
        double sumP = 0;
        double sumI_pow2 = 0;

        for (int i = 0, length = instrumentReturnRates.length; i < length; i++){
            double P = instrumentReturnRates[i];
            double I = indexReturnRates[i];

            sumI += I;
            sumP += P;
            sumIP += I * P;
            sumI_pow2 += I * I;
        }

        int periodSize = instrumentReturnRates.length + 1;

        double beta = (sumIP - (sumI * sumP) / (periodSize - 1))
                / (sumI_pow2 - ((sumI * sumI) / (periodSize - 1)));

        double medI = sumI / (periodSize - 1);
        double medP = sumP / (periodSize - 1);

        double alfa = medP - beta * medI;

        double sumDPfixedP_pow2 = 0;
        double sumDfixedPmedP_pow2 = 0;
        double sumDPmedP_pow2 = 0;

        for (int i = 0, length = instrumentReturnRates.length; i < length; i++){
            double P = instrumentReturnRates[i];
            double I = indexReturnRates[i];
            double fixedP = alfa + I * beta;

            sumDPfixedP_pow2 += (P - fixedP) * (P - fixedP);
            sumDfixedPmedP_pow2 += (fixedP - medP) * (fixedP - medP);
            sumDPmedP_pow2 += (P - medP) * (P - medP);
        }

        double determination = 1 - (sumDPfixedP_pow2 / sumDPmedP_pow2);
        double determinationLin = sumDfixedPmedP_pow2 / sumDPmedP_pow2;

        if (determination < 0 || determination > 1) {
            throw new IllegalStateException("Determination is " + determination);
        }

        return percent(determination);
//        return determinationLin;
    }

    public static double determination(List<Quote> instrumentQuotes, List<Quote> indexQuotes) {
        if (instrumentQuotes.size() != indexQuotes.size() || instrumentQuotes.size() < 2) {
            throw new IllegalArgumentException("Both quotes size must be the same and grater than 1.\n"
                    + "Instrument quotes size = " + instrumentQuotes.size() + "\nIndex quotes size = " + indexQuotes.size());
        }

        ArrayList<Double> instrumentReturnRates = new ArrayList<Double>();
        List<Double> indexReturnRates = new ArrayList<Double>();

        Iterator<Quote> instrumentQuoteIterator = instrumentQuotes.iterator();
        Iterator<Quote> indexQuoteIterator = indexQuotes.iterator();
        Quote prevInstrumentQuote = instrumentQuoteIterator.next();
        Quote prevIndexQuote = indexQuoteIterator.next();
        while (instrumentQuoteIterator.hasNext()) {
            Quote currInstrumentQuote = instrumentQuoteIterator.next();
            Quote currIndexQuote = indexQuoteIterator.next();

            instrumentReturnRates.add(returnRate(prevInstrumentQuote, currInstrumentQuote));
            indexReturnRates.add(returnRate(prevIndexQuote, currIndexQuote));

            prevInstrumentQuote = currInstrumentQuote;
            prevIndexQuote = currIndexQuote;
        }

        Double[] instrumentRR = new Double[instrumentReturnRates.size()];
        Double[] indexRR = new Double[indexReturnRates.size()];

        return determination(instrumentReturnRates.toArray(instrumentRR), indexReturnRates.toArray(indexRR));
    }

    public static Double[] boxDoubleArray(double[] doubles) {
        Double[] boxedDoubles = new Double[doubles.length];
        int i = 0;
        for (double aDouble : doubles) {
            boxedDoubles[i++] = aDouble;
        }
        return boxedDoubles;
    }

    public static double[] unboxDoubleArray(Double[] doubles) {
        double[] unboxedDoubles = new double[doubles.length];
        int i = 0;
        for (double aDouble : doubles) {
            unboxedDoubles[i++] = aDouble;
        }
        return unboxedDoubles;
    }

//    public static double determination(List<Quote> instrumentQuotes, List<Quote> indexQuotes) {
//        try {
//            if (instrumentQuotes.size() != indexQuotes.size() || instrumentQuotes.size() < 2) {
//                throw new IllegalArgumentException("Both quotes size must be the same and grater than 1.\n"
//                        + "Instrument quotes size = " + instrumentQuotes.size() + "\nIndex quotes size = " + indexQuotes.size());
//            }
//
//            StringBuilder logBuilder = new StringBuilder("\nLog:");
//            StringBuilder instrumentQuotesStringBuilder = new StringBuilder("\nInstrument quotes:");
//            StringBuilder indexQuotesStringBuilder = new StringBuilder("\nIndex quotes:");
//
//            Collections.sort(instrumentQuotes);
//            Collections.sort(indexQuotes);
//
//            double sumIP = 0;
//            double sumI = 0;
//            double sumP = 0;
//            double sumI_pow2 = 0;
//
//            Iterator<Quote> instrumentQuoteIterator = instrumentQuotes.iterator();
//            Iterator<Quote> indexQuoteIterator = indexQuotes.iterator();
//            Quote prevInstrumentQuote = instrumentQuoteIterator.next();
//            Quote prevIndexQuote = indexQuoteIterator.next();
//            while (instrumentQuoteIterator.hasNext()) {
//                Quote currInstrumentQuote = instrumentQuoteIterator.next();
//                Quote currIndexQuote = indexQuoteIterator.next();
//
//                double P = returnRate(prevInstrumentQuote, currInstrumentQuote);
//                double I = returnRate(prevIndexQuote, currIndexQuote);
//
//                sumI += I;
//                sumP += P;
//                sumIP += I * P;
//                sumI_pow2 += I * I;
//
//                instrumentQuotesStringBuilder.append("\n\t\tprevC = ").
//                        append(prevInstrumentQuote.getClosePrice()).
//                        append("\tcurrC = ").append(currInstrumentQuote).
//                        append("\trate = ").append(P);
//                indexQuotesStringBuilder.append("\n\t\tprevC = ").
//                        append(prevIndexQuote.getClosePrice()).
//                        append("\tcurrC = ").append(currIndexQuote).
//                        append("\trate = ").append(I);
//
//                prevInstrumentQuote = currInstrumentQuote;
//                prevIndexQuote = currIndexQuote;
//            }
//
//            int periodSize = indexQuotes.size();
//
//            double beta = (sumIP - (sumI * sumP) / (periodSize - 1))
//                    / (sumI_pow2 - ((sumI * sumI) / (periodSize - 1)));
//
//            double medI = sumI / (periodSize - 1);
//            double medP = sumP / (periodSize - 1);
//
//            double alfa = medP - beta * medI;
//
//            double sumDPfixedP_pow2 = 0;
//            double sumDfixedPmedP_pow2 = 0;
//            double sumDPmedP_pow2 = 0;
//
//            instrumentQuoteIterator = instrumentQuotes.iterator();
//            indexQuoteIterator = indexQuotes.iterator();
//            prevInstrumentQuote = instrumentQuoteIterator.next();
//            prevIndexQuote = indexQuoteIterator.next();
//            while (instrumentQuoteIterator.hasNext()) {
//                Quote currInstrumentQuote = instrumentQuoteIterator.next();
//                Quote currIndexQuote = indexQuoteIterator.next();
//
//                double P = returnRate(prevInstrumentQuote, currInstrumentQuote);
//                double I = returnRate(prevIndexQuote, currIndexQuote);
//                double fixedP = alfa + I * beta;
//
//                sumDPfixedP_pow2 += (P - fixedP) * (P - fixedP);
//                sumDfixedPmedP_pow2 += (fixedP - medP) * (fixedP - medP);
//                sumDPmedP_pow2 += (P - medP) * (P - medP);
//
//                prevInstrumentQuote = currInstrumentQuote;
//                prevIndexQuote = currIndexQuote;
//            }
//
//            double determination = 1 - (sumDPfixedP_pow2 / sumDPmedP_pow2);
//            double determinationLin = sumDfixedPmedP_pow2 / sumDPmedP_pow2;
//
//            logBuilder.append(instrumentQuotesStringBuilder.toString()).
//                    append(indexQuotesStringBuilder.toString()).
//                    append("\nsumI = ").append(sumI).
//                    append("\nsumP = ").append(sumP).
//                    append("\nsumIP = ").append(sumIP).
//                    append("\nsumI_pow2 = ").append(sumI_pow2).
//                    append("\nperiodSize = ").append(periodSize).
//                    append("\nmedI = ").append(medI).
//                    append("\nmedP = ").append(medP).
//                    append("\nalfa = ").append(alfa).
//                    append("\nbeta = ").append(beta).
//                    append("\nsumDPfixedP_pow2 = ").append(sumDPfixedP_pow2).
//                    append("\nsumDfixedPmedP_pow2 = ").append(sumDfixedPmedP_pow2).
//                    append("\nsumDPmedP_pow2 = ").append(sumDPmedP_pow2).
//                    append("\ndetermination = ").append(determination).
//                    append("\ndeterminationLin = ").append(determinationLin);
//
//            if (determination < 0 || determination > 1) {
//                throw new IllegalStateException("Determination is " + determination
//                        + " " + logBuilder.toString());
//            }
//
////        return percent(determination);
//            return determination;
//
//        } catch (Exception e) {
//            StringBuilder parametersStringBuilder = new StringBuilder("Instrument quotes:\n");
//            for (Quote quote : instrumentQuotes) {
//                if (quote != null) {
//                    parametersStringBuilder.append(quote.toString()).append("\n");
//                }
//            }
//
//            parametersStringBuilder.append("Index quotes:\n");
//            for (Quote quote : indexQuotes) {
//                if (quote != null) {
//                    parametersStringBuilder.append(quote.toString()).append("\n");
//                }
//            }
//
//            throw new ClassificatorRuntimeException("Failed to calculate determination. Parameters are:\n"
//                    + parametersStringBuilder + "\nException is: ", e);
//        }
//    }

    public static int percent(double d) {
        return (Double.isNaN(d) || d < 0) ? 0 : (int) (d * 100);
    }
}
